/**
 * 共享内存是所有进程间通信方式中最快的，因为数据拷贝的次数很少
 * 就对于简单的从stdin中读取数据，向stdout输出数据这种情况而言
 * 
 * 管道需要经历 4 + 2次拷贝数据 stdin > 输入缓冲区 > 我们自己创建的缓冲区buffer[] > 管道 
 * > 我们自己创建的缓冲区buffer[] > 输出缓冲区 > stdout(2是拷贝到输入缓冲区，和拷贝出输出缓冲区，因为每种方式都有所以可以不考虑)
 * 
 * 而共享内存只需2 + 2次，上述过程中减少出入管道时的拷贝就行了
 * 
 * 但是管道还有缺点，其中一个就是它没有同步和互斥的机制，不能够保护数据
 * 为了解决这个不足，我们可以加两根或一根管道
 * 
 *     写 ------> 读
 * 输入端 共享内存 输出端 
 *     读 <------ 写
 * 
 * 根据管道的特性，读端在得到写端的信息之前会一直阻塞时等待
 * 当输入端向共享内存写完数据后就向第一个管道发送一个信号，然后堵塞在第二个管道处
 * 
 * 而输出端在得到输入端从第一根管道传来的信号后的就会解除阻塞并向向共享内存读取数据
 * 读取完数据后再通过第二根管道向输入端发送信号
 * 这样输入端得到信号后就会再次向共享内存写入数据，重复上述操作
*/

#pragma once
#ifndef _COMMON_H_
#define _COMMON_H_

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

// PATH_NAME 和 PROJECT_ID 用来生成标识共享内存的key值
#define PATH_NAME "./"
#define PROJECT_ID 0x112234

// 共享内存的大小推荐是4kb的整数倍，因为内核开辟内存块的基本单位为page(4kb)
// 如果不是4kb的整数倍，OS也会开辟4kb整数倍的空间
// 如果MAX_SIZE为4097(4096 + 1)，OS也会开辟两页的空间(4096 + 4096)
// 但是用户还是只能用4097字节的空间，因为我给你 != 你能用
#define MAX_SIZE 4096


// 得到共享内存的key值
key_t getKey(const char* path_name = PATH_NAME, int project_id = PROJECT_ID) {
    key_t key = ftok(path_name, project_id);
    if (key < 0) {
        std::cerr << "getKey: " << errno << ": " << strerror(errno) << std::endl;
        exit(-1);
    }

    return key;
}

// 辅助函数
int _getShm(key_t key, int shm_flag) {
    int shm_id = shmget(key, MAX_SIZE, shm_flag);
    if (shm_id < 0) {
        std::cerr << "_getShm: " << errno << ": "<< strerror(errno) << std::endl;
        exit(-1); 
    }

    return shm_id;
}

// 得到key值所对应的已经创建的共享内存的shm_id
int getShm(key_t key) {
    return _getShm(key, IPC_CREAT);
}

// 用key开辟一块新的共享内存空间，如果key已经有对应的共享内存了就会退出报错
int creatShm(key_t key) {
    return _getShm(key, IPC_CREAT | IPC_EXCL | 0600);
}


// 将进程地址空间与共享内存进行挂接
void* attachShm(int shm_id) {
    void* start = shmat(shm_id, nullptr, 0);
    if ((long long)start == -1L) { // 该系统为64位系统，指针的大小为8字节，需要强转为long long 类型
        std::cerr << "attachShm: " << errno << ": " << strerror(errno) << std::endl;
        exit(-1);
    }

    return start;
}

// 将进程地址空间与共享内存去关联
void detachShm(void* shm_address) {
    if (shmdt(shm_address) == -1) {
        std::cerr << "detachShm: " << errno << ": " << strerror(errno) <<std::endl;
        exit(-1);
    }
}

// 删除共享内存空间
void deleteShm(int shm_id) {
    if (shmctl(shm_id, IPC_RMID, nullptr) == -1) {
        std::cerr << "deleteShm: " << errno << ": " << strerror(errno) <<std::endl;
        exit(-1);
    }
}

#endif